package com.spotalog.shared.model;

import java.util.Arrays;
import java.util.List;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;

import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;

import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Range;
import com.google.common.collect.Ranges;

@Data
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class BoundingBox {
	private double westLongitude;
	private double southLatitude;
	private double eastLongitude;
	private double northLatitude;

	/**
	 * For JAX-RS compliance
	 * 
	 * @param asString
	 * @return
	 */
	public static BoundingBox valueOf(String asString) {
		List<String> coords = ImmutableList.copyOf(Splitter.on(",").split(
				asString));
		Preconditions
				.checkArgument(coords.size() >= 4,
						"Cannot create BoundingBox from [%s]! Must have 4 doubles (W-S-E-N)");
		double west = Double.valueOf(coords.get(0));
		double south = Double.valueOf(coords.get(1));
		double east = Double.valueOf(coords.get(2));
		double north = Double.valueOf(coords.get(3));
		return new BoundingBox(west, south, east, north);
	}

	public static BoundingBox from(Location southWest, Location northEast) {
		Preconditions.checkNotNull(southWest,
				"southWest location cannot be null!");
		Preconditions.checkNotNull(northEast,
				"northEast location cannot be null!");
		return new BoundingBox(southWest.getLongitude(),
				southWest.getLatitude(), northEast.getLongitude(),
				northEast.getLatitude());
	}

	public BoundingBox(double westLongitude, double southLatitude,
			double eastLongitude, double northLatitude) {
		Preconditions.checkArgument(
				Math.abs(westLongitude) <= Location.MAX_LONGITUDE,
				"westLongitude must be [%s, %s] (was %s)",
				Location.MIN_LONGITUDE, Location.MAX_LONGITUDE, westLongitude);
		Preconditions.checkArgument(
				Math.abs(southLatitude) <= Location.MAX_LATITUDE,
				"southLatitude must be [%s, %s] (was %s)",
				Location.MIN_LATITUDE, Location.MAX_LATITUDE, southLatitude);
		Preconditions.checkArgument(
				Math.abs(eastLongitude) <= Location.MAX_LONGITUDE,
				"eastLongitude must be [%s, %s] (was %s)",
				Location.MIN_LONGITUDE, Location.MAX_LONGITUDE, eastLongitude);
		Preconditions.checkArgument(
				Math.abs(northLatitude) <= Location.MAX_LATITUDE,
				"northLatitude must be [%s, %s] (was %s)",
				Location.MIN_LATITUDE, Location.MAX_LATITUDE, northLatitude);
		this.westLongitude = westLongitude;
		this.southLatitude = southLatitude;
		this.eastLongitude = eastLongitude;
		this.northLatitude = northLatitude;
	}

	public boolean contains(Location location) {
		boolean contains = false;
		if (location != null) {
			Range<Double> longitudeRange = Ranges.open(getWestLongitude(),
					getEastLongitude());
			Range<Double> latitudeRange = Ranges.open(getSouthLatitude(),
					getNorthLatitude());
			contains = longitudeRange.contains(location.getLongitude())
					&& latitudeRange.contains(location.getLatitude());
		}
		return contains;
	}

	public boolean containsAny(Location... locations) {
		boolean any = false;
		if (locations != null) {
			any = containsAny(Arrays.asList(locations));
		}
		return any;
	}

	public boolean containsAny(Iterable<? extends Location> locations) {
		boolean any = false;
		if (locations != null) {
			any = Iterables.any(locations, createCondition(this));
		}
		return any;
	}

	public boolean containsAll(Location... locations) {
		boolean any = false;
		if (locations != null) {
			any = containsAll(Arrays.asList(locations));
		}
		return any;
	}

	public boolean containsAll(Iterable<? extends Location> locations) {
		boolean all = false;
		if (locations != null && locations.iterator().hasNext()) {
			all = Iterables.all(locations, createCondition(this));
		}
		return all;
	}

	private static final Predicate<Location> createCondition(
			final BoundingBox box) {
		return new Predicate<Location>() {
			@Override
			public boolean apply(Location input) {
				return box.contains(input);
			}
		};
	}
}
